// $Id$
// Licensed under Apache License Version 2.0
// See: http://www.apache.org/licenses/LICENSE-2.0.html
package de.servicezombie.tcmdb.rest.service;

import java.io.InputStream;
import java.util.List;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.dao.DataRetrievalFailureException;
import org.springframework.data.mongodb.core.query.Criteria;
import org.springframework.util.Assert;

import de.servicezombie.cxf.JaxbUnmarshallerFascade;
import de.servicezombie.cxf.WebserviceTemplate;
import de.servicezombie.cxf.WebserviceTemplateFactory;
import de.servicezombie.tcmdb.TcmdbRecordNotFoundException;
import de.servicezombie.tcmdb.dao.mongo.AuthorDao;
import de.servicezombie.tcmdb.dao.mongo.MongoUtils;
import de.servicezombie.tcmdb.dao.mongo.TestcaseDefinitionDao;
import de.servicezombie.tcmdb.rest.DocumentType;
import de.servicezombie.tcmdb.rest.DocumentTypeMatcher;
import de.servicezombie.tcmdb.rest.RestPathUtils;
import de.servicezombie.tcmdb.rest.RestTransformerSupport;
import de.servicezombie.tcmdb.rest.SameInstanceTransformer;
import de.servicezombie.tcmdb.rest.vo.ActivityStatus;
import de.servicezombie.tcmdb.rest.vo.WsAuthor;
import de.servicezombie.tcmdb.rest.vo.WsListResult;
import de.servicezombie.tcmdb.rest.vo.WsTestcaseDefinition;

public class TestcaseDefinitionServiceRestImpl implements TestcaseDefinitionService {

	private static final Logger log = LoggerFactory.getLogger(TestcaseDefinitionServiceRestImpl.class);

	private JaxbUnmarshallerFascade unmarshallerFascade;
	private TestcaseDefinitionDao testcaseDefinitionDao;
	private AuthorDao authorDao;

	public void setUnmarshallerFascade(JaxbUnmarshallerFascade unmarshallerFascade) {
		this.unmarshallerFascade = unmarshallerFascade;
	}

	public void setTestcaseDefinitionDao(TestcaseDefinitionDao testcaseDefinitionDao) {
		this.testcaseDefinitionDao = testcaseDefinitionDao;
	}

	public void setAuthorDao(AuthorDao authorDao) {
		this.authorDao = authorDao;
	}

	/* ChKa TODO test this function (Oct 16, 2011) */
	private WsListResult<WsTestcaseDefinition> findByApplication(String applicationName, DocumentType documentType) {

		Criteria criteria = new Criteria();
		criteria.and("applications.name").is(applicationName);

		switch (documentType) {
			case ACTIVE:
				criteria.and("status").is(ActivityStatus.OUTDATED).not();
				break;
			case ARCHIVED:
				criteria.and("status").is(ActivityStatus.OUTDATED);
				break;
		}

		List<WsTestcaseDefinition> dbResult = testcaseDefinitionDao.findByCriteria(criteria);

		RestTransformerSupport restTransformerSupport = new RestTransformerSupport();
		WsListResult<WsTestcaseDefinition> wsResult = restTransformerSupport.toWsListResult(
				dbResult, new SameInstanceTransformer<WsTestcaseDefinition>());

		return wsResult;
	}

	@Override
	public void save(
			String definitionId,
			InputStream definitionStream) {

		if (log.isDebugEnabled()) {
			log.debug("save: >> " + definitionId + ", " + definitionStream);
		}

		WsTestcaseDefinition definition = unmarshallerFascade.unmarshall(
				definitionStream, WsTestcaseDefinition.class);

		assertValidInput(definition);

		if (RestPathUtils.isNewRecordRequested(definitionId)) {
			testcaseDefinitionDao.saveNew(definition);
		}
		else {
			definition.setId(definitionId);
			testcaseDefinitionDao.saveReplace(definition);
		}

		WebserviceTemplate template = new WebserviceTemplateFactory().build();
		template.getWebserviceResponse().setBody(definition);

		if (log.isDebugEnabled()) {
			log.debug("save: << " + definition);
		}

	}

	private void assertValidInput(WsTestcaseDefinition definition) throws IllegalArgumentException {
		try {
			loadAuthorFromDatabase(definition);
		}
		catch (DataRetrievalFailureException e) {
			throw new IllegalArgumentException("Unable to find author " + definition.getOwner() + " in database", e);
		}
		Assert.notEmpty(definition.getApplications(), "No applications attached to testcase definition " + definition);
		
		MongoUtils.invalidateId(definition);
	}

	private void loadAuthorFromDatabase(WsTestcaseDefinition definition) {		
		Assert.notNull(definition.getOwner() , "No author provided in testcase definition " + definition);
		WsAuthor author = authorDao.load(definition.getOwner().getId());
		definition.setOwner(author);		
	}

	@Override
	public void archive(String definitionId) {
		if (log.isDebugEnabled()) {
			log.debug("archive: >> " + definitionId);
		}

		WsTestcaseDefinition definition = testcaseDefinitionDao.load(definitionId);
		definition.setStatus(ActivityStatus.OUTDATED);
		testcaseDefinitionDao.saveReplace(definition);

		if (log.isDebugEnabled()) {
			log.debug("save: << " + definition);
		}

		WebserviceTemplate template = new WebserviceTemplateFactory().build();
		template.getWebserviceResponse().setBody(definition);
	}

	@Override
	public void getDefinitionsByApplication(String type, String applicationName) {
		if (log.isDebugEnabled()) {
			log.debug("getDefinitionsByApplication: >> " + type + ", " + applicationName);
		}

		WsListResult<WsTestcaseDefinition> wsResult = findByApplication(applicationName, RestPathUtils.toTypeEnum(type));

		if (log.isDebugEnabled()) {
			log.debug("getDefinitionsByApplication: << " + wsResult);
		}
		WebserviceTemplate template = new WebserviceTemplateFactory().build();
		template.getWebserviceResponse().setBody(wsResult);
	}

	@Override
	public void getDefinitionsById(String type, String definitionId) throws TcmdbRecordNotFoundException {
		if (log.isDebugEnabled()) {
			log.debug("getDefinitionsById: >> " + type + ", " + definitionId);
		}
				
		WsTestcaseDefinition definition = testcaseDefinitionDao.load(definitionId);
		
		if(isNotMatchingDocumentType(definition, type)) {
			throw new TcmdbRecordNotFoundException("record is not available with requested type " + type + " / " + definitionId);
		}
		
		if (log.isDebugEnabled()) {
			log.debug("getDefinitionsById: << " + definition);
		}
		WebserviceTemplate template = new WebserviceTemplateFactory().build();
		template.getWebserviceResponse().setBody(definition);
	}

	private boolean isNotMatchingDocumentType(WsTestcaseDefinition definition, String type) {
		DocumentType documentType = RestPathUtils.toTypeEnum(type);
		return ! new DocumentTypeMatcher(documentType).isMatching(definition);
	}

}
